﻿using LightCAD.Runtime;
using LightCAD.Three;
using static System.Formats.Asn1.AsnWriter;
using Color = LightCAD.MathLib.Color;

namespace LightCAD.Model
{
   
    public class Model3DEditRuntime: Doc3dEditRuntime
    {
        public enum ControlsTypeEnum
        {
            Su,
            Revit,
            LightCAD
        }
        private SuControls suControls;
        private RvtControls rvtControls;
        private OrbitControls orbControls;
        private Group ModelGroup;
        private Group SectionGroup;
        private Group noDrawingGroup;
        private Group buildingsGroup;
        private Group drawingsGroup;
        private ConcurrentDictionary<LcDrawing, Group> drawingGroup=new ConcurrentDictionary<LcDrawing, Group>();
        private ConcurrentDictionary<LcBuilding, Group> buildingGroup = new ConcurrentDictionary<LcBuilding, Group>();
        private ClipBox clipBox;
 
        public IModel3DEditControl Control { get; set; }
        public ILightCADControls CameraControl;

        public Model3DEditRuntime(DocumentRuntime docRt, IModel3DEditControl control,ICommandControl commandControl):base(docRt,control,commandControl)
        {
            this.Name="模型";
            this.EditorType= EditorType.Model3d;
            this.Control = control;
            this.DocRt.Document.ModelSpace.ObjectChangedAfter += ModelSpace_ObjectChangedAfter;
            this.DocRt.Document.ModelSpace.PropertyChangedAfter += ModelSpace_PropertyChangedAfter;
            this.DocRt.Action.SelectsChanged += DocumentAction_SelectsChanged;
            this.Control.OnLevelFilterChanged += Control_OnLevelFilterChanged;
            this.CommandCenter = new CommandCenter(this, commandControl);
            this.SelectMats.ForEach(s => s.clippingPlanes = Clipplanes);
        
        }

        public void ChangeControls(ControlsTypeEnum conType)
        {
            switch (conType)
            {
                case ControlsTypeEnum.Su:
                    this.orbControls.Enable(false);
                    this.rvtControls.Enable(false);
                    this.suControls.Clone(this.CameraControl);
                    this.CameraControl = suControls;
                    this.CameraControl.Enable(true);
                    break;
                case ControlsTypeEnum.Revit:
                    this.orbControls.Enable(false);
                    this.suControls.Enable(false);
                    this.rvtControls.Clone(this.CameraControl);
                    this.CameraControl = rvtControls;
                    this.CameraControl.Enable(true);
                    break;
                case ControlsTypeEnum.LightCAD:
                    this.suControls.Enable(false);
                    this.rvtControls.Enable(false);
                    this.orbControls.Clone(this.CameraControl);
                    this.CameraControl = orbControls;
                    this.CameraControl.Enable(true);
                    break;
                default:
                    break;
            }
        }
        public string ShowSections()
        {
           return this.Control?.InvokeMethod("ShowSections")?.ToString();
        }

        private void Control_OnLevelFilterChanged(List<LcLevel> levels)
        {
            foreach (var item in buildingGroup)
            {
                var bgrp = item.Value;
                for (int i = 0; i < bgrp.children.Length; i++)
                {
                    var lvlGrp = bgrp.children[i];
                    var lvl = lvlGrp.ext["Level"] as LcLevel;
                    lvlGrp.visible = levels.Any(l => lvl == l);
                } 
            }
            this.Control.RefreshControl();
        }

        private ListEx<Material> SelectMats = new ListEx<Material> { new MeshBasicMaterial{ color=new Color(0x0000FF),transparent=true,opacity=0.5 }};
        private ConcurrentDictionary<int, ListEx<Material>> OriginMaterials = new ConcurrentDictionary<int, ListEx<Material>>();
       
        private bool updating=false;
        private void ModelSpace_PropertyChangedAfter(object? sender, PropertyChangedEventArgs e)
        {
            update();
        }
        private void ModelSpace_ObjectChangedAfter(object? sender, ObjectChangedEventArgs e)
        {
            update();
        }
        void update()
        {
            if (updating) return;
            updating = true;
            Task.Run(() => { 
            Thread.Sleep(10);
            this.OnUpdate();
            updating = false;
            });
        }
      
        //private Mesh testBox;
        protected override void OnInit()
        {
            base.OnInit();
            this.camera.position.Copy(syncCameraOffset);
            this.camera.lookAt(new Vector3());
            this.ModelGroup = new Group();
            this.ModelGroup.name="模型组";
            scene.add(this.ModelGroup);

            this.SectionGroup=new Group();
            this.SectionGroup.name="剖切组";
            this.SectionGroup.visible = false;
            scene.add(this.SectionGroup);

            this.clipBox = new ClipBox(this.SectionGroup, UnSelectChannel);
            this.noDrawingGroup = new Group();
            this.noDrawingGroup.name = "无图元素组";
            this.ModelGroup.add(this.noDrawingGroup);

            this.drawingsGroup = new Group();
            this.drawingsGroup.name = "未使用的图框元素组";
            this.ModelGroup.add(this.drawingsGroup);

            this.buildingsGroup = new Group();
            this.buildingsGroup.name = "单体元素组";
            this.ModelGroup.add(this.buildingsGroup);

            camera.layers.enable(ClipPlane.ControlPointLayerChanel);
            this.suControls = new SuControls(camera, this.Control)
            {
                screenSpacePanning = true,
                enabled = false
            };

            this.suControls.AddEventListener("change", (e) =>
            {
                this.Control.RefreshControl();
            });

            this.rvtControls = new RvtControls(camera, this.Control)
            {
                screenSpacePanning = true,
                enabled = false
            };

            this.rvtControls.AddEventListener("change", (e) =>
            {
                this.Control.RefreshControl();
            });

            this.orbControls = new OrbitControls(camera, this.Control)
            {
                screenSpacePanning = true
            };

            this.orbControls.AddEventListener("change", (e) =>
            {
                this.Control.RefreshControl();
            });

            this.CameraControl = this.orbControls;
            update();
        }
        protected override void OnDestory()
        {
            base.OnDestory();
            this.CameraControl.Dispose();
        }

        protected override void OnSizeChanged(Bounds bounds)
        {
            base.OnSizeChanged(bounds);
            this.CameraControl.Update();
        }
        protected override void OnMouseDown(MouseEventRuntime e)
        {
            downTime = DateTime.Now;
            RayCasterClipBoxControlPoint();
        }
        protected override void OnMouseMove(MouseEventRuntime e)
        {
            base.OnMouseMove(e);
            if (clipMove)
            {
                clipPlane.Move(camera, mouseLoc);
                clipBox.ClipModel(this.ModelGroup, true);
                this.Control.RefreshControl();
            }
        }
        protected override void OnMouseUp(MouseEventRuntime e)
        {   
            if (clipMove)
            {
                clipMove = false;
                clipPlane.UnDrag();
                this.CameraControl.Enable(true);
            }
            else
            {
                if (downTime != null && (DateTime.Now - downTime).Value.TotalMilliseconds <= 200)
                {
                    if (EnableSelect)
                    {
                        var intersects = RayCaster();
                        bool needRefresh = false;
                        if (this.SectionGroup.visible)
                        {
                            intersects.RemoveAll(inter => !clipBox.Box.ContainsPoint(inter.point));
                        }
                        var interSect = intersects.FirstOrDefault(o => o.target is Mesh);
                        if (interSect != null)
                        {
                            var mesh = interSect.target as Mesh;
                            var selEle = mesh.ext["Element"] as LcElement;
                            if (selEle != null)
                            {
                                SelectEle(new List<LcElement> { selEle });
                            }
                            for (int i = 0; i < EleMeshes[selEle.Id].Length; i++)
                            {
                                var eObj3d = EleMeshes[selEle.Id][i];
                                if (eObj3d.ext["Level"] == mesh.ext["Level"] && eObj3d is Mesh)
                                    SelectMesh(eObj3d as Mesh);
                            }
                        }
                        else
                        {
                            var ele3Ds = this.DocRt.Action.SelectedElements.FindAll(e => e is IElement3d);
                            UnSelectEle(ele3Ds);
                            var meshes = SelectedMeshs.Values.ToList();
                            for (int i = 0; i < meshes.Count; i++)
                            {
                                var mesh = meshes[i];
                                UnSelectMesh(mesh);
                                needRefresh = true;
                            }
                        }
                        if (needRefresh)
                            this.Control.RefreshControl();
                    }
                }
            }
            downTime = null;

        }

        #region Events


        private bool clipMove = false;
        private ClipPlane clipPlane = null;
        private DateTime? downTime = null;
        Raycaster EleRaycaster = new Raycaster();
        private void RayCasterClipBoxControlPoint()
        {
            var  raycaster =new Raycaster().setFromCamera(mouseLoc, camera);
            raycaster.layers.set(ClipPlane.ControlPointLayerChanel);
            var intersects = raycaster.intersectObject(this.scene, true);
            if (intersects.Count > 0)
            {
                var mesh = intersects[0].target as Mesh;
                if (mesh.ext.ContainsKey("ClipPlane"))
                {
                    clipMove = true;
                    this.CameraControl.Enable(false);
                    clipPlane = mesh.ext["ClipPlane"] as ClipPlane;
                    clipPlane.Drag();
                }
                this.Control.RefreshControl();
            }
        }
       
        #region Hover

        private bool isPointerHovering;
        private System.Timers.Timer hoverTimer;
        private void HoverTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
        {
            hoverTimer.Stop();
            this.isPointerHovering = true;
            HoverEventStart();
        }
        private void HoverEventStart()
        {

        }
        private void HoverEventEnd()
        {

        }

        #endregion
       
        public bool EnableSelect { get; set; } = true;
        private ConcurrentDictionary<int,Mesh> SelectedMeshs = new ConcurrentDictionary<int, Mesh>();
        private void DocumentAction_SelectsChanged(object? sender, System.EventArgs e)
        {
            var sv= SelectedMeshs.Values.ToList();
            sv.ForEach(mesh => { UnSelectMesh(mesh); });
            var selEles= this.DocRt.Action.SelectedElements.FindAll(e => e is IElement3d);
            selEles.ForEach(se =>
            {
                if (EleMeshes.ContainsKey(se.Id))
                    EleMeshes[se.Id].ForEach(o => { if (o is Mesh) SelectMesh(o as Mesh); });
            });
            this.Control.RefreshControl();
        }
        private void SelectMesh(Mesh mesh)
        {
            OriginMaterials.GetOrAdd(mesh.id, mesh.materials);
            mesh.materials = SelectMats;
            this.SelectedMeshs.GetOrAdd(mesh.id, mesh);
        }
        private void UnSelectMesh(Mesh mesh)
        {
            if (mesh.materials == SelectMats)
                mesh.materials = OriginMaterials[mesh.id];
            this.SelectedMeshs.Remove(mesh.id,out var nmesh);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="selEles"></param>
        private void SelectEle(List<LcElement> selEles)
        {
            try
            {
                this.DocRt.Action.SelectElements(this, selEles);
            }
            catch (Exception ex)
            {
                Debug.Print(ex.Message+ex.StackTrace);
            }
            finally
            {
            }
        }
        private void UnSelectEle(List<LcElement> selEles)
        {
            this.DocRt.Action.ClearSelects(this,selEles.FindAll(e => e.IsSelected));
        }
       
        #endregion
        public override Group GetExtGroup(string grpName)
        {
            var basicGroup = new string[] { "模型组", "剖切组", "无图元素组", "未使用的图框元素组", "单体元素组" };
            if (basicGroup.Contains(grpName))
                return null;
            return base.GetExtGroup(grpName);
        }
        
        void OnUpdate()
        {
            Debug.Print("Update:" + DateTime.Now);
            try
            {
                var doc = this.DocRt.Document;
                var result = ElementFilter.GroupByDrawings(doc.ModelSpace.Elements);
                this.Control.LoadDoc(doc);
                if (result.Item1.Count > 0)
                {
                    var usedDrawings = new List<LcDrawing>();
                    buildingsGroup.removeAll();
                    foreach (var building in doc.Buildings)
                    {
                        var used = CreateBuilding(building, result.Item1);
                        usedDrawings.AddRange(used);
                    }
                    drawingsGroup.removeAll();
                    foreach (var drawing in result.Item1.Keys)
                    {
                        if (usedDrawings.Contains(drawing)) continue;
                        CreateDrawing(drawing, result.Item1[drawing]);
                    }
                }
                this.noDrawingGroup.removeAll();
                if (result.Item2.Count > 0)
                {
                    CreateNoDrawingElements(result.Item2);
                }
            }
            catch (Exception ex)
            {


            }
            this.Control.RefreshControl();
        }
        ModelSpace GetModelSpace(ElementSpace eleSpace)
        {
            var drawings = eleSpace.Elements.Where(e =>
            {
                if (e is LcDrawing dw)
                    return dw.buildingBasePoint != null;
                return false;
            }).Select(e=>e as LcDrawing).ToListEx();
            var bindedDrawings = drawings.FindAll(d =>
            eleSpace.Document.Buildings.FirstOrDefault(b => 
            b.Levels.FirstOrDefault(l => l.Drawings.Contains(d)) != null) != null);
            var noBindDrawings = drawings.FindAll(d => !bindedDrawings.Contains(d));
            //通过图纸进行元素分组

            var modelSpace = new ModelSpace();
            var modelArea = new ModelArea();
            var worldGrp = new ModelGroup() { Name = "世界" };
            var worldSite = new ModelSite() { Name = "世界" }.AddGroups(worldGrp);
            var drawGroup = new Dictionary<LcDrawing, ModelGroup>();
            foreach (var ele in eleSpace.Elements)
            {
                if (ele is LcDrawing) continue;
                if (drawings.Length > 0)
                {
                    var isAdded = false;
                    foreach (LcDrawing drawing in drawings)
                    {
                        if (ele.BoundingBox.IntersectsBox(drawing.BoundingBox))
                        {
                            if (!drawGroup.TryGetValue(drawing, out ModelGroup grps))
                            {
                                grps =new ModelGroup();
                                grps.Transform.Position.X -= drawing.buildingBasePoint.Point.X;
                                grps.Transform.Position.Y -= drawing.buildingBasePoint.Point.Y;
                                drawGroup.Add(drawing, grps);
                            }
                            if (ele is IComponentInstance cpt)
                                grps.AddNodes(new ModelNode { Component = cpt });
                            isAdded = true;
                        }
                    }
                    if (!isAdded)
                    {
                        if (ele is IComponentInstance cpt)
                        {
                            worldGrp.AddNodes(new ModelNode() { Component = cpt });
                        }
                    }
                }
                else
                {
                    if (ele is IComponentInstance cpt)
                    {
                        worldGrp.AddNodes(new ModelNode() { Component = cpt });
                    }
                }
            }

            modelSpace.AddAreas(modelArea);
            modelArea.AddSites(worldSite);
            foreach (var building in eleSpace.Document.Buildings)
            {
                foreach (var lvl in building.Levels)
                {
                    lvl.Transform.Position.Z = lvl.Elevation;
                    foreach (var drawing in lvl.Drawings)
                    {
                        lvl.AddGroups(drawGroup[drawing]);
                    }
                }
                modelArea.AddSites(building);
            }
            foreach (var drawing in noBindDrawings)
            {
                worldGrp.AddGroups(drawGroup[drawing]);
            }
            return modelSpace;
        }
        //private List<string>usedNames=new List<string>();
        void RenderModelSpace(ModelSpace modelSpace) 
        {
            for (int i = 0; i < modelSpace.Areas.Length; i++)
            {
                var area = modelSpace.Areas[i];
            }
           
        }
        private ListEx<Plane> Clipplanes = new ListEx<Plane>();
        
        private ConcurrentDictionary<long, ListEx<Object3D>> EleMeshes = new ConcurrentDictionary<long, ListEx<Object3D>>();
        private void CreateNoDrawingElements(List<LcElement> elements)
        {
            CreateElements(this.noDrawingGroup,null ,elements);
        }
        private void CreateElements(Group grp,LcLevel level,List<LcElement> elements) 
        {
            var eles = elements.Where(e => e is IElement3d).ToList();
            if (eles.Count == 0) return;
            grp.removeAll();
            foreach (var ele in eles)
            {
                List<Object3D> objs = null;
                if (ele is LcSolid3d)
                {
                    var solidAction = (ele as IElement3d).Rt3DAction as Solid3dAction;
                    objs = solidAction.Render(ele as LcSolid3d, null);
                }
                else
                {
                    var cptIns = ele as IComponentInstance; 
                    var cptAction = cptIns.Rt3DAction as IComponentAction;
                    cptAction.SetDocEditor(this);
                    objs = cptAction.Render(cptIns);
                }
                
                for (int i = 0; i < objs.Count; i++)
                {
                    var obj = objs[i];
                    //OBJ要被Clipplanes剖切
                    if (obj is IMaterialObject matObj) 
                    {
                        var mats = matObj.getMaterials();
                        for (int j = 0; j < mats.Length; j++)
                        {
                            mats[j].clippingPlanes = Clipplanes;
                        }
                    }
                    obj.name = ele.Id.ToString();
                    obj.ext["Element"] = ele;
                    obj.ext["Level"] = level;

                }

                EleMeshes.AddOrUpdate(ele.Id, id => objs.ToListEx(), (id, existObjs) =>
                {
                    existObjs.RemoveAll(m => m.ext["Level"] == level);
                    existObjs.Push(objs.ToArray());
                    return existObjs;
                });
                grp.add(objs.ToArray());
            }
        }

        private void CreateDrawing(LcDrawing drawing, List<LcElement> lcElements)
        {
            var drawingGrp = drawingGroup.GetOrAdd(drawing, (d) =>
            {
                var grp = new Group { name = d.Name };
                return grp;
            });
            drawingsGroup.add(drawingGrp);
            CreateElements(drawingGrp,null, lcElements);
        }

        private List<LcDrawing> CreateBuilding(LcBuilding building, Dictionary<LcDrawing, List<LcElement>> item1)
        {
            var buildingGrp= buildingGroup.GetOrAdd(building, (b) => 
            {
                var grp = new Group { name = b.Name };
                return grp; 
            });
            buildingGrp.removeAll();
            buildingsGroup.add(buildingGrp);
            var usedDrawings=new List<LcDrawing>();
            foreach (var level in building.Levels)
            {
                var z = level.Elevation;
                var lvlGrp=new Group { name = level.Name };
                lvlGrp.position.Z = z;
                lvlGrp.ext["Level"] = level;
                buildingGrp.add(lvlGrp);
                for (int j = 0; j < level.Drawings.Count; j++)
                {
                    var drawing = level.Drawings[j];
                    if (drawing.buildingBasePoint == null)
                        continue;
                    var drawingGroup = new Group();
                    var basePoint = drawing.buildingBasePoint.Point;
                    drawingGroup.position.Set(-basePoint.X, -basePoint.Y, 0);
                    CreateElements(drawingGroup, level,drawing.Elements.Values.ToList());
                    lvlGrp.add(drawingGroup);
                    usedDrawings.Add(drawing);
                }
            }
            return usedDrawings;
        }

      
        private readonly Vector3 syncCameraOffset= new Vector3(-10000,-10000,50000);
        //public virtual void SyncViewPort2D(ViewportRuntime  viewportRt) 
        //{
        //    if (this.camera is OrthographicCamera)
        //    {
        //        var vc = viewportRt.DcsToWcs.MultiplyPoint(new Vector2d(0, 0));
        //        var scale = viewportRt.Viewport.Scale;
        //        var orCamera= (OrthographicCamera)this.camera;
        //        orCamera.zoom = scale/2*((double)canvas.Width/viewportRt.Viewport.Width);
        //        orCamera.position.AddVectors(new Vector3(vc.X,vc.Y,0), syncCameraOffset);
        //        var target = new Vector3(vc.X, vc.Y, 0);
        //        //orCamera.up.set(0, 1, 0);
        //        orCamera.lookAt(target);
        //        controls.target.Copy(target);
        //    }
        //    this.Control.RefreshControl();
        //}
        private bool clipBoxEnable = false;
        public void EnbaleClipBox() 
        {
            clipBoxEnable = !clipBoxEnable;
            if (clipBoxEnable)
            {
                this.SectionGroup.visible = true;
                this.clipBox.Set(GeoUtil.ComputeWorldBBox(this.ModelGroup), this.Clipplanes);
                this.renderer.localClippingEnabled = true;
            }
            else
            {
                this.SectionGroup.visible = false;
                this.renderer.localClippingEnabled = false;
            }
            this.Control.RefreshControl();
        }
        public override void EnableCameraControl(bool enable)
        {
            this.CameraControl.EnableRotate(enable);
            this.CameraControl.Enable(enable);
            this.CameraControl.ClearState();
        }
        public override ListEx<Raycaster.Intersection> RayCaster(Raycaster eleRaycaster = null, Action<Camera> cameraAction = null)
        {
            if (eleRaycaster == null)
                eleRaycaster = EleRaycaster;
            return base.RayCaster(eleRaycaster, cameraAction);
        }
    }
   
}
